import os
import time
from SCons.Environment import Environment
from SCons.Subst import Literal
from SCons.Script import SConscript,AddOption,GetOption
from SCons.Script.SConscript import SConsEnvironment,Return

# ----------配置自定义命令行选项----------
AddOption('--buildlib',
                      dest = 'buildlib',
                      type = 'string',
                      help = 'Enter the name of the library that you want to compile')

# ----------配置编译参数----------
TARGET_NAME = 'binary/demo'  #主程序名
SOURCE_DIR = 'source'       #源文件所在目录.搜索SConscript时用
INCLUDE_DIR = 'source'      #头文件所在目录.搜索头文件时用
LIBRARY_DIR = 'library'     #库文件所在目录.库文件只能在此目录,不能在子目录
VERSION_FILE = 'source/app/main.c'  #保存软件版本，编译时间的文件。如果不需要此类信息，则可不指定VERSION_FILE
# VERSION_FILE = ''

# 选择目标平台
# PLATFORM = 'arm_linux'
# PLATFORM = 'arm_none'
PLATFORM = 'x86-64'

# 根据目标平台，选择编译器及所在路径
if PLATFORM == 'arm_linux':
    COMPILER_PATH = '/home/wmk/mytools/arm-cortex_a8-linux-gnueabi-4.7.3/bin'
    COMPILER_PREFIX = 'arm-cortex_a8-linux-gnueabi-'
elif PLATFORM == 'arm_none':
    COMPILER_PATH = '/usr/bin'
    COMPILER_PREFIX = 'arm-none-eabi-'
elif PLATFORM == 'x86-64':
    COMPILER_PATH = '/usr/bin'
    COMPILER_PREFIX = ''
else:
    print("error:please Select the build platform ")
    SConsEnvironment.Exit(-1)


# ----------配置scons编译环境----------
env = Environment()

# print(env.Dump())
# scons编译时的环境变量(执行环境)和系统环境变量隔离。此处将编译器路径加入执行环境，否则编译时找不到编译器
env.AppendENVPath('PATH',COMPILER_PATH)
# 文件的访问时间更新后，将编译源文件
env.Decider('timestamp-newer')  

if len(VERSION_FILE) > 0:
    # VERSION_FILE中保存编译时间。每次工程更改，无论VERSION_FILE有没有修改，都需要重新编译VERSION_FILE以更新编译时间。
    current_time = time.time()
    os.utime(VERSION_FILE, (current_time, current_time))    # 修改文件的访问时间和修改时间为当前时间



env['CC'] = COMPILER_PREFIX + 'gcc'
env['AS'] = COMPILER_PREFIX + 'gcc'
env['AR'] = COMPILER_PREFIX + 'ar'
env['CXX'] = COMPILER_PREFIX + 'g++'
env['LINK'] = COMPILER_PREFIX + 'gcc'#链接时，gcc会自动调用ld，并传递相关参数
env['OBJSUFFIX'] = '.o'#指定目标文件后缀
env['PROGSUFFIX'] = '.elf'#指定可执行文件后缀

# 编译库的工作由库中的SConscript具体执行。不要在此之前在env中添加编译选项，否则会传递到库中SConscript
buildlib_option = GetOption('buildlib')
if buildlib_option:
    libraries = []
    if buildlib_option == 'all':
        for file in os.listdir(LIBRARY_DIR):
            dir = os.path.join(LIBRARY_DIR,file)
            if os.path.isdir(dir):
                libraries.append(dir)
    else:
        dir = os.path.join(LIBRARY_DIR,buildlib_option)
        if os.path.isdir(dir):
            libraries.append(dir)

    if not libraries:
        print('没有在{}目录内搜索到需要编译的动态库'.format(LIBRARY_DIR))
    else:
        print('需要编译的动态库为:')
        for line in libraries:
            print(line)


    for library in libraries:
        script = os.path.join(library,'SConscript')
        if os.path.isfile(script):
            SConscript(script,exports = 'env')
        else:
            print('Error:{} 不存在.'.format(script))
    Return()

# lib_name = GetOption('buildlib')
# if lib_name:
#     script = os.path.join(LIBRARY_DIR,lib_name,'SConscript')
#     if os.path.isfile(script):
#         SConscript(script,exports = 'env')
#         Return()
#     else:
#         print('Error:{} does not exist.PATH={}'.format(script))
#         SConsEnvironment.Exit(-1)

# ----------编译主程序----------

# C编译选项
C_FLAGS = []
C_FLAGS.append('-O0')       #若想调试，代码不能优化
C_FLAGS.append('-g')
C_FLAGS.append('-std=c99')
env['CFLAGS'] = C_FLAGS

# C和C++都可以使用的编译选项
CC_FLAGS = []
CC_FLAGS.append('-Wall')
env['CCFLAGS'] = CC_FLAGS

# C++的编译选项
CXX_FLAGS = []
env['CXXFLAGS'] = CXX_FLAGS

# 前往指定目录下寻找SConscript、头文件路径、动态库
SCRIPTS = []
INCLUDE_PATH = []
LIBRARIES = []

for root,dirs,files in os.walk(SOURCE_DIR):
    for file in files:
        if file == 'SConscript':
            script_path = os.path.join(root,file)
            SCRIPTS.append(script_path)
else:
    print('{}目录下的SConscript文件为:'.format(SOURCE_DIR))
    for line in SCRIPTS:
        print(line)

for root,dirs,files in os.walk(INCLUDE_DIR):
    for file in files:
        if file.endswith('.h'):
            if root not in INCLUDE_PATH:
                INCLUDE_PATH.append(root)
else:
    print('编译时的搜索的头文件路径为:')
    for line in INCLUDE_PATH:
        print(line)

shared_library_prefix = env['LIBPREFIX']    #动态库的前缀
shared_library_suffix = env['SHLIBSUFFIX']  #动态库的后缀
for file in os.listdir(LIBRARY_DIR):
    if file.endswith(shared_library_suffix) and file[0:3] == shared_library_prefix:
        path,ext = os.path.splitext(file)# 去除动态库前缀和后缀
        LIBRARIES.append(path[3:])
else:
    print('编译时链接的工程内的动态库为:')
    for line in LIBRARIES:
        print(line)

# 查看所有找到的SConscript和包含头文件的路径.DEBUG用
# print('SCRIPTS:{}'.format(SCRIPTS))
# print('INCLUDE_PATH:{}'.format(INCLUDE_PATH))
# print('LIBRARIES:{}'.format(LIBRARIES))

# -I,编译时头文件的搜索路径
env['CPPPATH'] = INCLUDE_PATH
# -l,编译时需要链接的动态库。只添加LIBRARY_DIR下没有的库
LIBRARIES.append('pthread')
env['LIBS'] = LIBRARIES
# -L,编译时动态库的搜索路径
env['LIBPATH'] = LIBRARY_DIR

# 假设LIBRARY_DIR = 'library'.则最后拼出来的效果为  '${ORIGIN}/../library'
rpath = "'$${ORIGIN}/../" + LIBRARY_DIR + "'"
# 程序运行时动态库的搜索路径
# Literal()使字符串按原样传递，而不展开变量。效果，-Wl,-rpath='${ORIGIN}/../library'
env['RPATH'] = Literal(rpath) 

SOURCES = []
for script in SCRIPTS:
    build_dir = os.path.join('build',os.path.dirname(script))
    SOURCES =  SOURCES + SConscript(script,exports = 'env',variant_dir = build_dir,duplicate = False)

objs = env.Object(SOURCES)
target = env.Program(target = TARGET_NAME, source=[objs])

# 使用size工具,查看可执行文件中.text .data .bss段所占字节
env.Command(TARGET_NAME + '.size', target, COMPILER_PREFIX + 'size' + ' --format=berkeley $SOURCE')
# 根据需求，使用objcopy生成.bin文件
# 根据需求，使用objdump生成.lst文件
# 根据需求，使用strip把可执行文件内的符号信息移除，减小对可执行文件的大小


# ----------清理----------
# 遍历库目录中的SConscript,会将库目录中的中间文件也删除
if GetOption('clean'):
    for root,dirs,files in os.walk(LIBRARY_DIR):
        for file in files:
            if file == 'SConscript':
                script_path = os.path.join(root,file)
                SConscript(script_path,exports = 'env')

print('-'*30)















